home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 22 / Cream of the Crop 22.iso / program / ctlib100.zip / INSTALL.LZH / COLLECT.TXT < prev    next >
Text File  |  1996-10-12  |  13KB  |  317 lines

  1.                         CHAPTER 6
  2.                        Collections
  3.  
  4. This chapter discusses in detail the Collection objects included in the Containers Library.  It describes how to use the collections and the different types of collections available.
  5.  
  6. In this chapter you will learn:
  7.  
  8.   - What are collections?
  9.   - How to use the collections
  10.   - The characteristics of each type of collection
  11.  
  12.  
  13. What are collections?
  14.  
  15. A collection is generic data structure that stores pointers to data items.  That is, when an item is inserted in the collection, the collection stores only the address at which the item is located in memory.  When the item is retrieved from the collection, the collection returns the memory address by which the data item can be referenced.  A collection is a more general concept than the traditional array, set or list.  As with all containers, collections are polymorphic, which means that they can store any type of data.  They are also dynamically sized, as most containers.  With a collection, you set an initial size, but it can grow at run time to accommodate the data stored in it.
  16.  
  17. The basic collection object by default assumes that data items in the collection are TObject descendants.  Sorted collections are collections of items sorted by a key and string collections are collections that assume that data items in the collection are dynamically allocated strings.  Both sorted and unsorted string collections are included in the library.
  18.  
  19. The Containers Library implements several new collections that complement Borland's TCollection object.  These new collections allow you to create huge collections or choose whether to store the collection's data in conventional memory, in expanded memory or even in a file on disk.  Additional methods in the containers' programming interface also provide improved access to data stored in a collection.
  20.  
  21. All collections require the use of dynamically allocated data.
  22.  
  23.  
  24. Huge collections
  25.  
  26. Huge collections in the Containers Library complement Borland's TCollection object, by providing an object that has no practical limit in the number of items it can store.  This section describes the process of using a THugeCollection object in your applications.  However, the topics discussed in here are not restricted to THugeCollection but are valid also for all the other collections included in the library.
  27.  
  28.  
  29. Creating a collection
  30.  
  31. As when using any other container in the library, the first thing you need to do to use a collection is to create the data type you wish to store in it.  In our examples, we'll use the following object definition:
  32.  
  33.   type
  34.     PContact = ^TContact;
  35.     TContact = object(TObject)
  36.         FirstName,
  37.         LastName,
  38.         Phone,
  39.         Company : PString;
  40.       constructor Init(ALastName, AFirstName, APhone,
  41.         ACompany: string);
  42.       destructor Done; virtual;
  43.     end;
  44.  
  45. Next, you must implement TContact's Init and Done methods, to allocate and dispose of the data in the object.
  46.  
  47.   constructor TContact.Init(ALastName, AFirstName, APhone,
  48.     ACompany : string);
  49.   begin
  50.     FirstName := NewStr(AFirstName);
  51.     LastName := NewStr(ALastName);
  52.     Phone := NewStr(APhone);
  53.     Company := NewStr(ACompany);
  54.   end;
  55.  
  56.   destructor TContact.Done;
  57.   begin
  58.     DisposeStr(FirstName);
  59.     DisposeStr(LastName);
  60.     DisposeStr(Phone);
  61.     DisposeStr(Company);
  62.   end;
  63.  
  64. Note that TContact.Done will be called automatically for each TContact object, when you dispose of the entire collection.
  65.  
  66.  
  67. Using the collection
  68.  
  69. Now you are ready to use the collection.  To start using a collection in your application, all you need to do is create an instance of the collection and start inserting items into it.  For example:
  70.  
  71.   var
  72.     ContactInfo : PHugeCollection;
  73.   
  74.   begin  { collect1.pas }
  75.   
  76.   { Create the collection }
  77.   ContactInfo := New(PHugeCollection, Init(50, 10));
  78.   
  79.   { Insert items into the collection }
  80.     with ContactInfo^ do
  81.     begin
  82.       Insert(New(PContact, Init('Lewis', 'Carl', 
  83.         '(506) 83-780', 'Running, Corp.')));
  84.       Insert(New(PContact, Init('Benton', 'Michael',
  85.         '(403) 33-973', 'ER, Inc.')));
  86.       Insert(New(PContact, Init('Wagner', 'Robert',
  87.         '(906) 11-230', 'Symphony, Ltd.')));
  88.       Insert(New(PContact, Init('Smith', 'John',
  89.         '(656) 75-843', 'InterComm, Corp.')));
  90.     end;
  91.   
  92.     DisplayContacts(ContactInfo);
  93.     Writeln;
  94.     DisplayFirst(ContactInfo);
  95.     Writeln;
  96.     DisplayLast(ContactInfo);
  97.     Writeln;
  98.     FindLastName(ContactInfo, 'Wagner');
  99.   
  100.     { Dispose of the collection }
  101.     { and all the objects in it }
  102.     Dispose(ContactInfo, Done);
  103.   end.
  104.  
  105.  
  106. Iterative methods
  107.  
  108. The DisplayContacts procedure shows an example of how to use a ForEach iterator, one of the several iterator methods supported by containers.  ForEach takes one parameter to a procedure, which must be a local procedure declared using the far model (either by using the far directive or the $F+ compiler directive).  This procedure takes one parameter, which is a pointer to an item stored in the container. 
  109.  
  110.   procedure DisplayContacts(ContactList: PSequence);
  111.  
  112.     procedure PrintInfo (Item : Pointer); far;
  113.     begin
  114.       with PContact(Item)^ do
  115.         Writeln(LastName^, '':15 - Length(LastName^),
  116.           FirstName^, '':15 - Length(FirstName^), Phone^,
  117.           '':20 - Length(Phone^), Company^, '':20 -
  118.           Length(Company^));
  119.     end;
  120.   
  121.   begin
  122.     ContactList^.ForEach(@PrintInfo);
  123.   end;
  124.  
  125. Another kind of iterator is the FirstThat method.  It will return the first item in the container that matches a condition.  Instead of taking a pointer to a far local procedure, it takes as a parameter a pointer to a far local boolean function:
  126.  
  127.   procedure FindLastName(ContactList: PSequence;
  128.     LastName: string);
  129.   var
  130.     Item : Pointer;
  131.     Index : LongInt;
  132.   
  133.     function MatchLastName(Item: Pointer): Boolean; far;
  134.     begin
  135.       MatchLastName := (LastName =
  136.         PContact(Item)^.LastName^);
  137.     end;
  138.  
  139.   begin
  140.     Item := ContactList^.FirstThat (@MatchLastName, Index);
  141.     Writeln('Item found with last name ''', LastName, ''':');
  142.     with PContact(Item)^ do
  143.       Writeln(LastName^, '':15 - Length(LastName^),
  144.         FirstName^, '':15 - Length(FirstName^), Phone^,
  145.         '':20 - Length(Phone^), Company^, '':20 -
  146.         Length(Company^));
  147.  
  148.     { not required }
  149.     ContactList^.DoneItem(Item); 
  150.   end;
  151.  
  152. FirstThat also takes an Index parameter, in which it will store the index of the item returned.  This value can be used as a seed value in other methods, as for instance, the NextThat method.  If used after a call to FirstThat, NextThat will return the second item in the container that satisfies a given condition:
  153.  
  154.   Item := ContactList^.FirstThat (@MatchLastName, Index);
  155.   ContactList^.DoneItem(Item);
  156.   Item := ContactList^.NextThat (@MatchLastName, Index);
  157.   ContactList^.DoneItem(Item);
  158.   
  159.  
  160. Using DoneItem
  161.  
  162. Notice the way the DoneItem method is used in the last two examples, and in the DisplayFirst and DisplayLast methods:
  163.  
  164.   procedure DisplayFirst(ContactList: PSequence);
  165.   var
  166.     Item : Pointer;
  167.     Index : LongInt;
  168.   begin
  169.     Item := ContactList^.First(Index);
  170.     {...}  
  171.  
  172.     { not required }
  173.     ContactList^.DoneItem(Item); 
  174.   end;
  175.  
  176.   procedure DisplayLast(ContactList: PSequence);
  177.   var
  178.     Item : Pointer;
  179.     Index : LongInt;
  180.   begin
  181.     Item := ContactList^.Last(Index);
  182.     {...}  
  183.  
  184.     { not required }
  185.     ContactList^.DoneItem(Item);
  186.   end;
  187.    
  188. DoneItem is always called after the item retrieved from the container is no longer needed.  Although collections keep all data in memory always and therefore, the use of DoneItem is not required, if you want to make your code data-structure independent you should always use DoneItem.  In the above examples, if you decide you don't want to use a collection anymore, but use a stream based container instead, you won't have to modify any of the code in the examples.  Depending on the container that you choose, all you will have to do is initialize the new object instead of the collection and recompile your program!  Note, however, that if the new container does not support the data type you are using, you will also need to change the type definition of your data.
  189.  
  190.  
  191. Sorted collections
  192.  
  193. A sorted collection is a collection that lets you store items sorted by key.  For using a sorted collection, the first thing you need to do is teach the collection how to sort the items.  For this purpose, you need to override the KeyOf method and in some cases, the Compare method also.
  194.  
  195. For creating a sorted list of contacts in our previous example, the first thing you must do is override the KeyOf method of the THugeSortedCollection object, as shown here:
  196.  
  197.   type
  198.     PSortedContactList = ^TSortedContactList;
  199.     TSortedContactList = object(THugeSortedCollection)
  200.       function KeyOf(Item: Pointer): Pointer; virtual;
  201.     end;
  202.  
  203.   function TSortedContactList.KeyOf(Item: Pointer): Pointer;
  204.   begin
  205.     KeyOf := PContact(Item)^.LastName;
  206.   end;
  207.  
  208. All you need to do now to create your sorted list of contacts, is to create an instance of a TSortedContactList instead of a normal THugeCollection:
  209.  
  210.   begin { collect2.pas }
  211.     {...}
  212.     ContactInfo := New(PSortedContactList, Init(50, 10));
  213.     {...}
  214.   end;
  215.  
  216. Is that simple!  These are usually also the same steps that you will follow when using any other sorted container in the library, like the sorted arrays, sorted linked lists, B trees, etc.
  217.  
  218.  
  219. String collections
  220.  
  221. String collections are a special type of collection that stores pointers to dynamically allocated strings.  Using a string collection is easy: simply create an instance of the string collection you want to use, and then start adding items: 
  222.  
  223.   begin  { collect3.pas }
  224.     {...}
  225.     StringList := New(PHugeStringCollection, Init(50, 10));   
  226.  
  227.     { Insert items into the collection }
  228.     with StringList^ do
  229.     begin
  230.       Insert(NewStr('Sky'));
  231.       Insert(NewStr('Absolute'));
  232.       Insert(NewStr('Temporary'));
  233.       Insert(NewStr('Field'));
  234.     end;
  235.  
  236.     {...}
  237.   
  238.     { Dispose of the collection }
  239.     { and all the objects in it }
  240.     Dispose(StringList, Done);
  241.   end.
  242.  
  243. Notice that when you dispose the collection, it will take care of deallocating all the strings for you.  Therefore, you don't have to worry about manually disposing of individual strings. 
  244.  
  245.  
  246. Using Search
  247.  
  248. In sorted sequences, you can use the Search method to find an item with a given key.  In the case of string collections, the key of an item in the collection is the item itself: 
  249.  
  250.   procedure FindString(StringList: PSequence;
  251.     SearchStr : string);
  252.   var
  253.     Index : LongInt;
  254.   begin
  255.     if StringList^.Search(@SearchStr, Index)
  256.       then begin
  257.              Writeln('String found:');
  258.              Writeln(PString(StringList^.At(Index))^)
  259.            end
  260.       else Writeln('String not found.');
  261.   end;
  262.  
  263.  
  264. Unsorted string collections
  265.  
  266. You can also create unsorted string collections by using the THugeUnSortedStrCollection object (see collect4.pas).  For searching for an item in an unsorted string collection, you can use one of the conditional iterative methods supported by the containers, like for example, the FirstThat method:
  267.  
  268.   procedure FindString(StringList: PSequence;
  269.     SearchStr : string);
  270.   var
  271.     Item : Pointer;
  272.     Index : LongInt;
  273.   
  274.     function MatchString(Str: PString): Boolean; far;
  275.     begin
  276.       MatchString := (SearchStr = Str^);
  277.     end;
  278.   
  279.   begin
  280.     Item := StringList^.FirstThat (@MatchString, Index);
  281.     if Item <> nil
  282.       then begin
  283.              Writeln('String found:');
  284.              Writeln(PString(Item)^);
  285.            end
  286.       else Writeln('String not found.');
  287.  
  288.     { not required }
  289.     StringList^.DoneItem(Item); 
  290.   end;
  291.  
  292. Calling the Search method in unsorted containers will result in a "Call to abstract method" error. 
  293.  
  294.  
  295. Stream collections
  296.  
  297. Stream collections are another type of collection, in which the data in the collection is stored in a stream, rather than in the heap.  For example, you can store the collection in expanded memory and save conventional memory for other uses.  Note, however, that stream collections store in the stream only the pointers to data items.  The data itself is always kept in the heap. 
  298.  
  299. Using a stream collection is as easy as using a normal collection.  All you have to do is create an instance of the stream collection that you want to use: 
  300.  
  301.   var
  302.     ContactInfo : PEmsCollection;
  303.  
  304.   begin  { collect5.pas }
  305.     {...}
  306.     ContactInfo := New(PEmsCollection, Init(50, 10));
  307.     if ContactInfo = nil
  308.       then begin
  309.              Writeln('No EMS memory available.');
  310.              Halt(1);
  311.            end;
  312.     {...}
  313.     Dispose(ContactInfo, Done);
  314.   end.
  315.  
  316.  
  317. By default, stream collections use a TMemoryStream to store the data in the collection.  If you wish to use a different type of stream, you must override the InitStream method.  This is what the TEmsXXXX collections do.